fix(lock): don't dereference a NULL locker in the priority accessors (#148) - #154
Merged
Conversation
Fixes #148. DB_ENV->set_lk_priority() and DB_ENV->get_lk_priority() crashed the library on a plain public-API call naming a locker id with no live locker: db_env_create(&e, 0); e->open(e, path, DB_CREATE|DB_INIT_LOCK|DB_INIT_MPOOL|DB_INIT_TXN|DB_INIT_LOG, 0600); e->set_lk_priority(e, 0, 100); /* SIGSEGV */ __lock_getlocker() with create == 0 reports 'no such locker' by returning 0 with a NULL locker rather than by returning an error -- its only assignment to *retp is at lock_id.c:408, after the 'if (sh_locker == NULL && create)' block. Both accessors tested only 'ret == 0' and then dereferenced. gdb: Program received signal SIGSEGV #0 __lock_set_lk_priority (priority=100) at ../src/lock/lock_method.c:483 #1 main () at r.c:13 Both now return EINVAL for a missing locker. The check is deliberately local to these two functions rather than a change to __lock_getlocker()'s contract: __lock_vec_pp() passes the same possibly-NULL locker into __lock_vec(), which handles it on purpose ('it's perfectly reasonable for there to be no locker; this is not an error'), so tightening the shared helper would break a caller that depends on the current behavior. The original report suspected __lock_vec_pp() was affected too; it is not, and I verified that by calling lock_vec() with DB_LOCK_PUT_ALL and DB_LOCK_UPGRADE_WRITE on an unused id -- both return 0. Regression test test/db/lock_priority_nullderef.c covers all three cases: EINVAL for the setter and getter on an unused id, and a set/get round trip on a LIVE locker so the fix cannot regress into simply disabling the feature. Proven to have teeth: reverting the two checks makes the runner report FAIL (rc=139, Segmentation fault). Wired into the CI regression-runner step. Found by the coverage-driver work in #147, which SIGSEGVed on the never-called getter surface.
Coccinelle convention checksNo new violations. ✅ Resolved since baseline (2) -- update dist/cocci/baseline.txt to lock these in. |
ABI diff vs
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #148. Found by the coverage-driver work in #147, which SIGSEGVed while exercising the never-called getter surface.
The crash
DB_ENV->set_lk_priority()andDB_ENV->get_lk_priority()killed the process on a plain public-API call naming a locker id with no live locker — ~12 lines, no fault injection, no invalid arguments:__lock_getlocker()withcreate == 0reports no such locker by returning 0 with a NULL locker, not by returning an error — its only assignment to*retpislock_id.c:408, after theif (sh_locker == NULL && create)block. Both accessors tested onlyret == 0and dereferenced.Why the check is local, not in the shared helper
__lock_vec_pp()(lock.c:93) passes the same possibly-NULL locker into__lock_vec(), which handles it deliberately:So tightening
__lock_getlocker()'s contract would break a caller that depends on today's behavior. The original report suspected__lock_vec_pp()was affected too — it is not, and I verified that rather than propagate it:lock_vec()withDB_LOCK_PUT_ALLand withDB_LOCK_UPGRADE_WRITEon an unused id both return 0 cleanly. Correction posted to #148.Test
test/db/lock_priority_nullderef.c(5 checks) covers all three cases —EINVALfor setter and getter on an unused id, and a set/get round trip on a live locker so the fix can't regress into merely disabling the feature:Proven to have teeth: reverting the two checks makes the runner report
FAIL (rc=139, Segmentation fault). Wired into the CI regression-runner step added in #153.Regression
5/5
test/dbrunners,test/lockmatrix,test/isolation,test/soakall PASS. (lock00*/txn00*TCL need--enable-test, which this local build lacks — covered by CI.)